﻿using System;
using System.Drawing;
using MathNet.Numerics.LinearAlgebra;

namespace GridRayTracer
{
  /// <summary>
  /// Ray tracing algorithm implementation.  
  /// </summary>
  [Serializable]
  public class RayTracer
  {
    public RayTracer(Scene scene)
    {
      Scene = scene;      
      FocalLength = 1.0;
      ViewPort = new ViewPort(-1, 1, -1, 1);
      ScreenWidth = 800;
      ScreenHeight = 600;
      ReflectionCount = 1;
      Background = Color.Black;
    }
    
    public Color GetColorAt(int x, int y)
    { 
      var dir = Vector.Create(new double[]
      {
        ViewPort.Left + (double)x / ScreenWidth * ViewPort.Width, 
        ViewPort.Bottom + (double)y / ScreenHeight * ViewPort.Height,
        FocalLength 
      });
      var ray = new Ray(Vector.Zeros(3), dir);
      return Trace(ray, ReflectionCount);
    }
    
    Color Trace(Ray ray, int remainingReflections)
    {
      var hit = Scene.HitTest(ray);
      if (hit == null)
      {        
        if (remainingReflections == ReflectionCount)
          return Background;
        return Scene.GetLightAt(ray.Start, ray.Direction);
      }
      
      Color color = hit.SceneObject.Surface.DiffuseColor;
      int red = color.R;
      int green = color.G;
      int blue = color.B;
      
      if (remainingReflections > 0)
      {
        var reflectedRay = hit.ReflectedRay;
        var reflectedColor = Trace(reflectedRay, remainingReflections - 1);
        red += (int)(reflectedColor.R * hit.SceneObject.Surface.ReflectionIntensity);
        green += (int)(reflectedColor.G * hit.SceneObject.Surface.ReflectionIntensity);
        blue += (int)(reflectedColor.B * hit.SceneObject.Surface.ReflectionIntensity);
      }
      else
      {
        var lightColor = Scene.GetLightAt(hit.HitPoint, hit.Normal);
        red += lightColor.R;
        green += lightColor.G;
        blue += lightColor.B;
      }
      
      return Color.FromArgb(Math.Min(255, red), Math.Min(255, green), Math.Min(255, blue));
    }

    public Scene Scene 
    {
      get;
      set;
    }

    public double FocalLength 
    {
      get;
      set;
    }

    public ViewPort ViewPort
    {
      get;
      set;
    }    
    
    public int ScreenWidth
    {
      get;
      set;
    }
    
    public int ScreenHeight
    {
      get;
      set;
    }
    
    public int ReflectionCount 
    {
      get;
      set;
    }
    
    public Color Background
    {
      get;
      set;
    }
  }

  [Serializable]
  public class ViewPort 
  {
    public ViewPort(double left, double right, double bottom, double top)
    {
      Left = left;
      Right = right;
      Bottom = bottom;
      Top = top;
    }
    
    public readonly double Left;
    public readonly double Right;
    public readonly double Bottom;
    public readonly double Top;
    
    public double Width 
    {
      get { return Right - Left; }
    }
    
    public double Height
    {
      get { return Top - Bottom; }
    }
    
    public double CenterX 
    {
      get { return (Left + Right) / 2; }
    }
    
    public double CenterY 
    {
      get { return (Bottom + Top) / 2; }
    }
  }
}
